﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Input;
using System.Drawing;
using Vortex.Drawing;

namespace Vortex.SceneFramework {

	/// <summary>
	/// Implements input event management for screen graph system
	/// </summary>
	public class InputManager {
		
		///<summary>Possible mouse event types</summary>
		protected enum MouseEventType {
			MouseMove,
			MouseDown,
			MouseUp,
			Click,
			DoubleClick,
			MouseOver,
			MouseOut,
			MouseWheel
		}

		private ScreenManager _sceneManager;
		
		private Node _pointedElement;
		private Node _hoveredElement;
		private Node _capturedElement;
		private MouseButton _captureButton;
		private Node _focusedElement;

		private Vector2 _location;
		private Vector2 _captureLocation;
		private Vector2 _previousLocation;


		/// <summary>
		/// Initializes a new instance of the <see cref="InputProcessor"/> class.
		/// </summary>
		/// <param name="graph">The graph to serve.</param>
		/// <param name="mouse">The mouse interface.</param>
		/// <param name="keyboard">The keyboard interface.</param>
		internal InputManager(ScreenManager sceneManager) {
			_sceneManager = sceneManager;

			_sceneManager.Mouse.ButtonDown += new MouseButtonActionHandler(MouseButtonDown);
			_sceneManager.Mouse.ButtonUp += new MouseButtonActionHandler(MouseButtonUp);
			_sceneManager.Mouse.Move += new MouseMoveActionHandler(MouseMove);
			_sceneManager.Mouse.Wheel += new MouseWheelActionHandler(MouseWheel);

			_sceneManager.Keyboard.KeyDown += new KeyActionHandler(KeyDown);
			_sceneManager.Keyboard.KeyUp += new KeyActionHandler(KeyUp);
			_sceneManager.Keyboard.EnterChar += new CharActionHandler(InputChar);
		}

		/// <summary>
		/// Updates the mouse pointer location.
		/// </summary>
		/// <param name="location">The location of cursor.</param>
		private void UpdateMousePointer(Point location) {
			_previousLocation = _location;
			_location = new Vector2(location);

			_pointedElement = null;

			foreach (Node element in _sceneManager.GetInputTargetElements()) {
				_pointedElement = element.FindElementAtPoint(_location);
				if (null != _pointedElement) break;
			}

			SetHoveredElement(_pointedElement);
		}

		/// <summary>
		/// Updates the hovered element.
		/// </summary>
		/// <param name="hoveredElement">The new hovered element.</param>
		private void SetHoveredElement(Node hoveredElement) {
			//check for captured element. If it is not captured - no other element can be hovered
			if (_capturedElement != null && hoveredElement != _capturedElement) {
				hoveredElement = null;
			}

			//checking for hovered element is changed
			bool changed = (_hoveredElement != hoveredElement);
			if (changed) {
				if (_hoveredElement != null) {
					//need to report mouse out
					RaiseMouseEvent(_hoveredElement, MouseEventType.MouseOut, false);
				}
			}
			
			//set new hovered element
			_hoveredElement = hoveredElement;
			
			//check changes in hovered element
			if (changed) {
				if (_hoveredElement != null) {
					//need to report mouse over
					RaiseMouseEvent(_hoveredElement, MouseEventType.MouseOver, false);

					//setup the game cursor
					Game.Cursor = _hoveredElement.Cursor;
				} else {
					//set default game cursor if any element is out of range
					Game.Cursor = _capturedElement != null ? _capturedElement.Cursor : GameCursor.Default;
				}
			}
		}

		/// <summary>
		/// Captures the specified element.
		/// </summary>
		/// <param name="capturedElement">Element to capture.</param>
		/// <param name="captureButton">The capture button.</param>
		private void CaptureElement(Node capturedElement, MouseButton captureButton) {
			if (capturedElement != null) {
				_capturedElement = capturedElement;
				_captureButton = captureButton;
				_captureLocation = _location;

				//check set focus
				TryFocusElement(capturedElement);
				

				//need to call mouse down here
				RaiseMouseEvent(capturedElement, MouseEventType.MouseDown, captureButton, 0, true);
			}

			/*
			//calculate focus change
			Element elementToFocus = capturedElement != null ? FindFirstFocusableElement(capturedElement) : null;
			if (FocusedElement != elementToFocus) {
				ReleaseFocus();
				SetFocus(elementToFocus);
			}
			*/
		}

		/// <summary>
		/// Releases the capture.
		/// </summary>
		/// <param name="suppressClickEvent">if set to <c>true</c> then click/double click will not be raised.</param>
		private void ReleaseCapture(bool suppressClickEvent) {
			//need to report mouse up 
			RaiseMouseEvent(_capturedElement, MouseEventType.MouseUp, _captureButton, 0, true);

			if (!suppressClickEvent && _capturedElement == _pointedElement) {
				//need to invoke click if event is generated inside of captured element
				RaiseMouseEvent(_capturedElement, MouseEventType.Click, _captureButton, 0, true);
			}

			_capturedElement = null;
			_captureButton = MouseButton.None;
		}


		/// <summary>
		/// Tries to focus the specified element.
		/// </summary>
		/// <param name="element">The element to focus.</param>
		private void TryFocusElement(Node element) {
			if (element.IsFocusable) {
				if (element != _focusedElement) {
					KillFocus();
					SetFocus(element);
				}
			} else {
				KillFocus();
			}
		}

		/// <summary>
		/// Sets the input focus to the specified element.
		/// </summary>
		/// <param name="element">The element.</param>
		private void SetFocus(Node element) {
			_focusedElement = element;
			_focusedElement.ProcessGainFocus();
		}

		/// <summary>
		/// Kills the input focus.
		/// </summary>
		private void KillFocus() {
			if (null != _focusedElement) {
				_focusedElement.ProcessLooseFocus();
				_focusedElement = null;
			}
		}

		/// <summary>
		/// Gets the key states.
		/// </summary>
		/// <returns>Key states flags</returns>
		private KeyStates GetKeyStates() {
			return
				(_sceneManager.Mouse.IsLeftDown ? KeyStates.LeftButton : KeyStates.None) |
				(_sceneManager.Mouse.IsRightDown ? KeyStates.RightButton : KeyStates.None) |
				(_sceneManager.Mouse.IsMiddleDown ? KeyStates.MiddleButton : KeyStates.None) |
				(_sceneManager.Keyboard.IsDown(Key.LeftControl) || _sceneManager.Keyboard.IsDown(Key.RightControl) ? KeyStates.Control : KeyStates.None) |
				(_sceneManager.Keyboard.IsDown(Key.LeftShift) || _sceneManager.Keyboard.IsDown(Key.RightShift) ? KeyStates.Shift : KeyStates.None) |
				(_sceneManager.Keyboard.IsDown(Key.LeftAlt) || _sceneManager.Keyboard.IsDown(Key.RightAlt) ? KeyStates.Alt : KeyStates.None);
		}

		/// <summary>
		/// Sends the event to target.
		/// </summary>
		/// <param name="target">The target.</param>
		/// <param name="eventType">Type of the event.</param>
		/// <param name="eventInfo">The event info.</param>
		private void SendEvent(Node target, MouseEventType eventType, MouseEventInfo eventInfo) {
			switch (eventType) {
				case MouseEventType.MouseMove:
					target.ProcessMouseMove(eventInfo);
					break;
				case MouseEventType.MouseOut:
					target.ProcessMouseOut(eventInfo);
					break;
				case MouseEventType.MouseOver:
					target.ProcessMouseOver(eventInfo);
					break;
				case MouseEventType.MouseDown:
					target.ProcessMouseDown(eventInfo);
					break;
				case MouseEventType.MouseUp:
					target.ProcessMouseUp(eventInfo);
					break;
				case MouseEventType.Click:
					target.ProcessClick(eventInfo);
					break;
				case MouseEventType.DoubleClick:
					target.ProcessDoubleClick(eventInfo);
					break;
			}
		}

		private void RaiseMouseEvent(Node originalTarget, MouseEventType eventType, bool bubbleEvents) {
			RaiseMouseEvent(originalTarget, eventType, MouseButton.None, 0, bubbleEvents);
		}

		private void RaiseMouseEvent(Node originalTarget, MouseEventType eventType, MouseButton button, int wheelDelta, bool bubbleEvents) {
			KeyStates keyStates = GetKeyStates();
			bubbleEvents = bubbleEvents && originalTarget.IsEventBubblingEnabled;

			if (!originalTarget.IsEnabled || !originalTarget.IsVisible) return;

			//what should I do here: send event to target, than if propagation is not stopped properly - to its parent up to root?
			Node element = originalTarget;
			while (null != element) {
				Transform2D elementScreenTransform = element.ScreenTransform;
				
				MouseEventInfo eventInfo = new MouseEventInfo(
					originalTarget,
					elementScreenTransform.ReverseMultiply(_location),
					button,
					wheelDelta,
					keyStates
				);

				//send event to be dispatched with element
				SendEvent(element, eventType, eventInfo);

				//next target is parent of this one
				//element = (bubbleEvents && !eventInfo.IsCanceled) ? element.Parent : null;
				element = null;
			}
		}

		#region Input Handler Methods 

		/// <summary>
		/// Mouses the button up.
		/// </summary>
		/// <param name="button">The mouse button which raised this event.</param>
		/// <param name="location">The location of event.</param>
		/// <param name="modKeys">Modification keys.</param>
		private void MouseButtonDown(MouseButtonActionInfo info) {
			UpdateMousePointer(info.Location);

			if (null == _capturedElement) {
				CaptureElement(_hoveredElement, info.Button);
			}
		}

		/// <summary>
		/// Mouses the button up.
		/// </summary>
		/// <param name="button">The mouse button which raised this event.</param>
		/// <param name="location">The location of event.</param>
		/// <param name="modKeys">Modification keys.</param>
		private void MouseButtonUp(MouseButtonActionInfo info) {
			UpdateMousePointer(info.Location);

			if (_captureButton == info.Button) {
				ReleaseCapture(false);
			}
		}

		/// <summary>
		/// Processes mouses move event.
		/// </summary>
		/// <param name="location">The cursor location.</param>
		/// <param name="modKeys">The modification keys.</param>
		private void MouseMove(MouseMoveActionInfo info) {
			UpdateMousePointer(info.Location);

			Node targetElement = _capturedElement ?? _pointedElement;
			if (null != targetElement) {
				RaiseMouseEvent(targetElement, MouseEventType.MouseMove, _captureButton, 0, true);
			}
		}

		private void MouseWheel(MouseWheelActionInfo info) {

		}

		/// <summary>
		/// Key up input event handler.
		/// </summary>
		/// <param name="key">The key was released.</param>
		private void KeyUp(VirtualKey key) {
			if (null != _focusedElement) {
				_focusedElement.ProcessKeyUp(new KeyboardEventInfo(_focusedElement, key, GetKeyStates()));
			}
		}

		/// <summary>
		/// Key down input event handler.
		/// </summary>
		/// <param name="key">The key was pressed.</param>
		private void KeyDown(VirtualKey key) {
			if (null != _focusedElement) {
				_focusedElement.ProcessKeyDown(new KeyboardEventInfo(_focusedElement, key, GetKeyStates()));
			}
		}

		/// <summary>
		/// Input char event handler method.
		/// </summary>
		/// <param name="charCode">The char code.</param>
		private void InputChar(char charCode) {
			if (null != _focusedElement) {
				_focusedElement.ProcessChar(new KeyboardEventInfo(_focusedElement, charCode, GetKeyStates()));
			}

		}

		#endregion
	}
}
